package com.spring.nio.service.file.util;

import com.alibaba.fastjson.JSON;
import com.spring.nio.business.file.service.dto.FileDto;
import com.spring.nio.business.user.dto.UserDTO;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.service.protocol.response.file.FileTransportResponse;
import com.spring.nio.service.protocol.response.file.TransportResponse;
import com.spring.nio.util.BasicUtil;
import com.spring.nio.util.LocalTime;
import com.spring.nio.util.NioUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 文件写事件解析类
 * @author spring
 * */
@Slf4j
public class NioParseWriteUtil {

    /**
     * 解析消息并发送
     * @param obj
     * @param socketChannelContext
     */
    public static Boolean parseSend(Object obj, SocketChannelContext socketChannelContext) {
        String jsonMessage = "";
        TransportResponse transportResponse = (TransportResponse) obj;
        byte b = Byte.parseByte(transportResponse.getFrameType(), 2);
        if(!Objects.isNull(transportResponse.getResponseName())) {
            if(b == 1 || b == 6) {
                // 文件上传或下载校验成功响应帧
                jsonMessage = JSON.toJSONString(transportResponse);
            } else if(b == 11) {
                // 文件删除帧
                jsonMessage = JSON.toJSONString((List) transportResponse.getData());
            }
        } else {
            if(b == 3 || b == 12) {
                // 用户列表刷新帧
                jsonMessage = JSON.toJSONString((List) transportResponse.getData());
            } else if(b == 4) {
                // 操作文件流数据，直接发送文件流数据
                return sendMessage((byte[]) ((FileTransportResponse) transportResponse).getStreamData(), transportResponse, socketChannelContext);
            } else if(b == 6 || b == 7 || b == 8) {
                //个人网盘树刷新帧、创建，修改
                jsonMessage = JSON.toJSONString((FileDto) transportResponse.getData());
            } else if(b == 19 || b == 20) {
                // 文件查询帧或服务监控帧
                jsonMessage = JSON.toJSONString(transportResponse.getData());
            } else {
                jsonMessage = JSON.toJSONString(transportResponse);
            }
        }

        // 发送数据
        return sendMessage(convertDataToBytes(jsonMessage, b), transportResponse, socketChannelContext);
    }

    /**
     * 数据转为bytes
     * @param jsonMessage
     * @param b 帧类型
     * @return
     */
    private static byte[] convertDataToBytes(String jsonMessage, byte b) {
        byte[] sendBytes = null;
        try {
            // 1、发送数据字节数组
            byte[] sendDataBytes = jsonMessage.getBytes("utf-8");
            // 2、发送数据字节数组长度
            byte[] sendDataBytesLength = BasicUtil.intToBytes(sendDataBytes.length);
            // 3、当前总字节数组长度 (总长度: 4B + 帧类型: 1B + 结束帧: 1B + 实际数据长度: 4B + 实际数据内容: ...)
            byte[] sumBytesLength = BasicUtil.intToBytes(4 + 1 + 1 + sendDataBytes.length + sendDataBytesLength.length);

            // 4、发送字节数组实例化
            sendBytes = new byte[4 + 1 + 1 + sendDataBytes.length + sendDataBytesLength.length];
            System.arraycopy(sumBytesLength, 0, sendBytes, 0, sumBytesLength.length); // 数据总长度4B
            sendBytes[4] = b; // 帧类型1B
            sendBytes[5] = (byte) 1; // 结束帧1B
            System.arraycopy(sendDataBytesLength, 0, sendBytes, 6, sendDataBytesLength.length); // 数据长度4B
            System.arraycopy(sendDataBytes, 0, sendBytes, (sendDataBytesLength.length + 6), sendDataBytes.length); // 数据字节
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return sendBytes;
    }

    /**
     * 发送数据
     * @param bytes
     * @param transportResponse
     * @param socketChannelContext
     */
    private static Boolean sendMessage(byte[] bytes, TransportResponse transportResponse, SocketChannelContext socketChannelContext) {
        if(null == bytes) {
            return Boolean.FALSE;
        }

        FileTransportResponse fileTransportResponse = null;
        try {
            // 发送数据
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length);
            byteBuffer.put(bytes);
            byteBuffer.flip();

            int alreadySendBytes = 0;
            while (byteBuffer.hasRemaining()) {
                alreadySendBytes += socketChannelContext.getSocketChannel().write(byteBuffer);

                // 如果是文件发送响应，打印详细日志
                if(transportResponse instanceof FileTransportResponse) {
                    fileTransportResponse = (FileTransportResponse) transportResponse;
                    /*log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioParseWriteUtil | --> 文件 = [{}] 流数据发送成功, 已发送字节数 = {}, 发送地址 = {}",
                        fileTransportResponse.getFileName(), alreadySendBytes, socketChannelContext.getRemoteAddress());*/
                } else {
                    log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioParseWriteUtil | --> 数据发送成功, 已发送字节数 = {}, 发送地址 = {}",
                        alreadySendBytes, socketChannelContext.getRemoteAddress());
                }

                if(alreadySendBytes == byteBuffer.limit()) {
                    // 如果已发送的数据字节数 == byteBuffer中limit指定的数量时，跳出hasRemain循环
                    break;
                }
            }

            return Boolean.TRUE;
            // 取消写事件注册,防止重复发送消息,重新注册读事件，否则无法继续进行事件读取
            //selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
            //selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ);
        } catch (IOException e) {
            handleWriteIOException(e, fileTransportResponse, socketChannelContext);
            // 通道写事件异常，只打印异常信息，不做关闭处理
            if(!Objects.isNull(fileTransportResponse)) {
                log.error("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioParseWriteUtil | --> 文件 = [{}] 发送线程出现IO异常, error = {}", fileTransportResponse.getFileName(), e.getMessage());
            } else {
                log.error("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioParseWriteUtil | --> 发送线程出现IO异常, error = {}", e.getMessage());
            }

            return Boolean.FALSE;
        }
    }

    /**
     * 处理写事件异常
     * @param e
     * @param fileTransportResponse
     * @param socketChannelContext
     * */
    private static void handleWriteIOException(IOException e, FileTransportResponse fileTransportResponse, SocketChannelContext socketChannelContext) {
        if(e instanceof SocketException) {
            if(StringUtils.contains(e.getMessage(), "Broken pipe (Write failed)")) {
                NioUtil.closedAndRelease(socketChannelContext, true, true, true);
                return;
            }
        }
    }
}
